mod authorization;
mod client;
mod crypto;
mod errors;
mod handlers;
mod sniper;
mod solana;

use crypto::generate_nonce;
use handlers::{
    buy_instruction_handler::buy_instruction, get_magiceden_token_data_handler::get_me_token_data,
    get_rarity_handler::get_rarity_handler,
};
use hyper::{server::Server, Method};
use listenfd::ListenFd;
use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;

use std::{collections::HashMap, convert::Infallible, str::FromStr, sync::Arc};

use futures::{SinkExt, TryFuture};
use serde_json::json;
use tokio::sync::Mutex;

use futures_util::StreamExt;

use warp::{ws::Message, Filter, Rejection};

async fn autoreload_start<F>(routes: F)
where
    F: Filter + Clone + std::marker::Send + 'static,
    <F::Future as TryFuture>::Ok: warp::Reply,
{
    let svc = warp::service(routes);

    let make_svc = hyper::service::make_service_fn(|_: _| {
        let svc = svc.clone();
        async move { Ok::<_, Infallible>(svc) }
    });

    let mut listenfd = ListenFd::from_env();

    let server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
        Server::from_tcp(l).unwrap()
    } else {
        Server::bind(&([127, 0, 0, 1], 3030).into())
    };

    server.serve(make_svc).await.unwrap();
}

fn with_db<T>(
    db: Arc<Mutex<T>>,
) -> impl Filter<Extract = (Arc<Mutex<T>>,), Error = Infallible> + Clone
where
    T: Send + Clone + 'static,
{
    warp::any().map(move || db.clone())
}

#[derive(Deserialize, Serialize)]
struct WithPublicKeyBody {
    public_key: String,
}

#[derive(Deserialize, Serialize)]
struct LoginBody {
    public_key: String,
    message: String,
}

async fn handshake_handler(
    db: Arc<Mutex<sled::Db>>,
    handshake: WithPublicKeyBody,
) -> Result<warp::reply::WithHeader<warp::reply::WithHeader<warp::reply::Json>>, Infallible> {
    let db = db.lock().await;

    let (server_public_key, server_private_key) = crypto::generate_keypair();
    let serpub_key = hex::encode(server_public_key.as_bytes());

    let public_key = match hex::decode(handshake.public_key) {
        Ok(value) => value,
        Err(_) => {
            let reply = warp::reply::json(&json!({
                "error": "Aborted decoding handshake public key"
            }));

            return Ok(warp::reply::with_header(
                warp::reply::with_header(reply, "Access-Control-Allow-Origin", "*"),
                "Access-Control-Allow-Headers",
                "*",
            ));
        }
    };

    let mut server_public_key_bytes = server_public_key.as_bytes().to_vec();
    let mut server_private_key_bytes = server_private_key.as_bytes().to_vec();

    let mut bytes: Vec<u8> = vec![];
    bytes.append(&mut server_public_key_bytes);
    bytes.append(&mut server_private_key_bytes);

    db.insert(public_key, bytes).unwrap();

    let reply = warp::reply::json(&json!({
        "public_key": serpub_key,
    }));

    Ok(warp::reply::with_header(
        warp::reply::with_header(reply, "Access-Control-Allow-Origin", "*"),
        "Access-Control-Allow-Headers",
        "*",
    ))
}

async fn get_nonce_handler(
    db: Arc<Mutex<HashMap<String, String>>>,
    hash_body: WithPublicKeyBody,
) -> Result<warp::reply::WithHeader<warp::reply::WithHeader<warp::reply::Json>>, Infallible> {
    let mut db = db.lock().await;
    let public_key = hash_body.public_key.clone();

    let hash = generate_nonce();
    let hash = hex::encode(hash);

    db.insert(public_key, hash.clone());

    let reply = warp::reply::json(&json!({ "nonce": hash }));

    Ok(warp::reply::with_header(
        warp::reply::with_header(reply, "Access-Control-Allow-Origin", "*"),
        "Access-Control-Allow-Headers",
        "*",
    ))
}

fn get_addresses() -> Vec<String> {
    vec![
        "7w94YY7PqbcVBYksneA2kVPjvVcmAGdWPzBHZufMjKkP".to_string(),
        "fvDDUtuXCtdiW31KgPrYFx4wEwaXJAmmkhxn4SS3kYt".to_string(),
        "B1dsgmprfXav6kqmyRhwEH93gxRse8C1uxRY1t7s7bwi".to_string(),
        "L9JvMnSXeXcwsAwc8vgs9ozF9JruTfve9rCzsNSqnT2".to_string(),
        "2SSDTLiLE9uFata49DoRLDTBxTcQeKfvwUcAEB74GGqE".to_string(),
        "LJC4jitYisE7vtrzxRzvLEQax7TFzrAmkrcAin8aAqX".to_string(),
        "8K14rg8gWD6wFSHeqLy9EjEXqHpKHz2248EVMbSfKTXy".to_string(),
        "7pakJiFG9uzqPhtngXdM3P8Lo5b5q9RLyg5n9szCzwnp".to_string(),
        "G9BQjXLV9Fo2ESLfRjY75VCaFNPL9Tb9oF4ykoHVNqVP".to_string(),
        "4NVwdYqNZqABErx7fguQt3MNSyGeDxaTjQYN7RQst6Hu".to_string(),
        "F9JwQLYLiJ42Yq4zJByr3grMmTnRsAQPHNwTr5YRvH1J".to_string(),
        "8mWrtv7Fv83frjQ3HMro1gAMmdMq5xdRkQggaBThk7Pi".to_string(),
        "GR6f5cVM2XFekUsjQHpLew3oF3oJfBsAZDeENTQn5Z4i".to_string(),
        "8K14rg8gWD6wFSHeqLy9EjEXqHpKHz2248EVMbSfKTXy".to_string(),
        "AJbh7y3g6DFzRKMaYTVx8iuUB23M4K1B4kTD6y8JGeTa".to_string(),
        "91jorZYNzkXBB8qNwjDRgasGYJWknTVvvmm82BzodqtS".to_string(),
        "2GKBkAg1st9NaUZQ48dTYXcvsR7Hzy6HcWRQ2xaZHgAz".to_string(),
        "9jyopxBEQt3WemdFG2w4D3Yu4R88dKaGLRD6J7B5mw8r".to_string(),
        "9XaK65rkvUqy8B26F92CkW7gkBbPrkCkPCoEcgoLqcUi".to_string(),
        "Gwm89BMKz5aYz3H3jrw6QNeFnmc9frVENUoHmqsPRWvF".to_string(),
        "6Q13ogcYHSdsDbqvqiVFwworA8VHUC3jfRzCjrEw5MaQ".to_string(),
        "BTxjwy6REmSrXHaA81exkwki1f41NeGYjPhQaggLLQft".to_string(),
        "4xaTfQmKownpRye3Hk1ourRR9ygajrBXmUkR5s5RwKPZ".to_string(),
        "9aqW17HLzL7W95ivfvPjE8Rf4CwgHeX9HfGZEZSWk8LJ".to_string(),
        "EDsquZ9jAxRTTcbnbm4p613h11HQp1FhDt8PiZA1QqeA".to_string(),
        "Dz5WjVY3A9qaKHBZ6wgvritiUJVrX9iY42RJJs1uH16S".to_string(),
        "AxBVK1aquYBVxKDabHtoMwBDCAjkb98s3g1H1LU71gmM".to_string(),
    ]
}

async fn wallet_contains_nfts(pubkey: String) -> Result<bool, errors::Error> {
    let nfts = solana::util::get_nft_wallet(pubkey).await;

    Ok(nfts
        .iter()
        .filter(|nft| {
            if nft["data"]["collection"].is_object()
                && nft["data"]["collection"]["name"].is_string()
            {
                return nft["data"]["collection"]["name"] == "StarAtlas";
            }

            false
        })
        .count()
        == 1)
}

async fn login_handler(
    db: Arc<Mutex<HashMap<String, String>>>,
    login_body: LoginBody,
) -> Result<
    warp::reply::WithHeader<warp::reply::WithHeader<warp::reply::WithHeader<warp::reply::Json>>>,
    Infallible,
> {
    let db = db.lock().await;

    match db.get(&login_body.public_key) {
        Some(nonce) => {
            let pubkey = Pubkey::from_str(&login_body.public_key.clone()).unwrap();
            let data =
                match solana::util::verify_signature(&pubkey, nonce.clone(), login_body.message)
                    .await
                {
                    Ok(verified) => {
                        println!(
                            "{:?}",
                            wallet_contains_nfts(login_body.public_key.clone()).await
                        );
                        if verified && get_addresses().contains(&login_body.public_key.clone()) {
                            match authorization::encode(nonce.to_string()) {
                                Ok(token) => json!({ "token": token }),
                                Err(_) => json!({"error": "Internal server error"}),
                            }
                        } else {
                            json!({"error": "Internal server error"})
                        }
                    }
                    Err(err) => json!({"error": "Singature is not verified"}),
                };

            let reply = warp::reply::json(&data);

            Ok(warp::reply::with_header(
                warp::reply::with_header(
                    warp::reply::with_header(reply, "Access-Control-Allow-Origin", "*"),
                    "Access-Control-Allow-Headers",
                    "*",
                ),
                "Content-Type",
                "application/json",
            ))
        }
        None => {
            let reply = warp::reply::json(&json!({"error": "Public key not found"}));

            Ok(warp::reply::with_header(
                warp::reply::with_header(
                    warp::reply::with_header(reply, "Access-Control-Allow-Origin", "*"),
                    "Access-Control-Allow-Headers",
                    "*",
                ),
                "Content-Type",
                "application/json",
            ))
        }
    }
}

fn with_jwt_token() -> impl Filter<Extract = (), Error = Rejection> + Clone + Copy {
    warp::header::<String>("Authorization")
        .and(warp::header::<String>("X-Nonce"))
        .and_then(|token, nonce| async move {
            match authorization::decode(token, &nonce) {
                Ok(token) => Ok(()),
                Err(err) => Err(warp::reject::custom(err)),
            }
        })
        .untuple_one()
}

#[tokio::main]
async fn main() {
    let db = sled::open("./clients.db").unwrap();
    let db = Arc::new(Mutex::from(db));

    let nonce_db: Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::from(HashMap::new()));

    let (tx, rx) = tokio::sync::watch::channel(json!({}));

    let tx = Arc::new(Mutex::from(tx));

    let me_tx = tx.clone();
    let coral_tx = tx.clone();

    tokio::task::spawn(async move {
        sniper::start_sniper(me_tx, "hausS13jsjafwWwGqZTUQRmWyvyxn9EQpqMwV1PBBmk")
            .await
            .unwrap();
    });

    tokio::task::spawn(async move {
        sniper::start_sniper(coral_tx, "M2mx93ekt1fmXSVkTrUL9xVFHkmME8HTUi5Cyc5aF7K")
            .await
            .unwrap();
    });

    let sniper_websocket = warp::path("sniper")
        .and(
            warp::query::<HashMap<String, String>>()
                .and_then(|query: HashMap<String, String>| async move {
                    match authorization::decode(
                        query.get("token").unwrap_or(&String::new()).to_string(),
                        query.get("nonce").unwrap_or(&String::new()),
                    ) {
                        Ok(token) => Ok(()),
                        Err(err) => Err(warp::reject::not_found()),
                    }
                })
                .untuple_one(),
        )
        .and(warp::ws())
        .map(move |ws: warp::ws::Ws| {
            println!("pizda");
            let mut rx = rx.clone();

            ws.on_upgrade(move |ws: warp::ws::WebSocket| {
                let (mut txx, _rx) = ws.split();

                async move {
                    while rx.changed().await.is_ok() {
                        let message = rx.borrow().to_string();

                        // println!("{message}");

                        let data = miniz_oxide::deflate::compress_to_vec_zlib(
                            message.as_str().as_bytes(),
                            5,
                        );

                        if let Err(_err) = txx.send(Message::binary(data)).await {
                            break;
                        }
                    }
                }
            })
        });

    let token_db: Arc<Mutex<HashMap<String, serde_json::Value>>> =
        Arc::new(Mutex::from(HashMap::new()));

    let nonce = warp::post()
        .and(warp::path("get_nonce"))
        .and(with_db::<HashMap<String, String>>(nonce_db.clone()))
        .and(warp::body::json::<WithPublicKeyBody>())
        .and_then(get_nonce_handler)
        .with(
            warp::cors()
                .allow_methods(&[Method::POST])
                .allow_header("content-type")
                .allow_any_origin(),
        );

    let login = warp::post()
        .and(warp::path("login"))
        .and(with_db::<HashMap<String, String>>(nonce_db.clone()))
        .and(warp::body::json::<LoginBody>())
        .and_then(login_handler)
        .with(
            warp::cors()
                .allow_methods(&[Method::POST])
                .allow_header("content-type")
                .allow_any_origin(),
        );

    let handshake = warp::post()
        .and(warp::path("handshake"))
        .and(with_db::<sled::Db>(db))
        .and(warp::body::json::<WithPublicKeyBody>())
        .and_then(handshake_handler);

    let authorized = with_jwt_token();

    let rarity = warp::path("rarity")
        .and(authorized)
        .and(warp::path::param::<String>())
        .and(warp::query::<HashMap<String, String>>())
        .and_then(get_rarity_handler)
        .with(
            warp::cors()
                .allow_methods(&[Method::GET])
                .allow_headers(vec!["Authorization", "X-Nonce"])
                .allow_any_origin(),
        );

    let buy_now = warp::path("buy_now")
        .and(authorized)
        .and(warp::query::<HashMap<String, String>>())
        .and_then(buy_instruction)
        .with(
            warp::cors()
                .allow_methods(&[Method::GET])
                .allow_headers(vec!["Authorization", "X-Nonce"])
                .allow_any_origin(),
        );

    let token_data = warp::path("token_data")
        .and(authorized)
        .and(warp::path::param::<String>())
        .and(warp::any().map(move || token_db.clone()))
        .and_then(get_me_token_data)
        .with(
            warp::cors()
                .allow_methods(&[Method::GET])
                .allow_headers(vec!["Authorization", "X-Nonce"])
                .allow_any_origin(),
        );

    let index = warp::path::end().map(|| warp::reply::html("<h1>Иди нахуй</h1>"));

    let index = index.or(nonce).or(login);

    let routes = index
        .or(sniper_websocket)
        .or(rarity)
        .or(buy_now)
        .or(token_data)
        .recover(errors::handle_rejection);

    // autoreload_start(routes).await;

    warp::serve(routes).run(([0, 0, 0, 0], 3030)).await;
}
